package com.ohos.connect.devicecloud.auth.modular.login.service.impl;

import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.google.gson.Gson;
import com.ohos.connect.devicecloud.auth.modular.login.model.dto.AuthAppPwdDTO;
import com.ohos.connect.devicecloud.auth.modular.login.model.dto.AuthPhoneValidCodeLoginDTO;
import com.ohos.connect.devicecloud.auth.modular.login.model.dto.PhoneValidCodeQueryDTO;
import com.ohos.connect.devicecloud.auth.modular.login.model.dto.ValidPhoneCodeDTO;
import com.ohos.connect.devicecloud.auth.modular.login.model.vo.*;
import com.ohos.connect.devicecloud.dev.api.DevEmailApi;
import com.ohos.connect.devicecloud.dev.email.DevEmailTemplateEnum;
import com.ohos.connect.devicecloud.mqtt.api.MqttApi;
import com.ohos.connect.devicecloud.mqtt.core.model.vo.MqttAccountVO;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.MqttMessage;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.ohos.connect.devicecloud.auth.api.SaBaseLoginUserApi;
import com.ohos.connect.devicecloud.auth.core.pojo.SaBaseClientLoginUser;
import com.ohos.connect.devicecloud.auth.core.pojo.SaBaseLoginUser;
import com.ohos.connect.devicecloud.auth.core.util.StpAppLoginUserUtil;
import com.ohos.connect.devicecloud.auth.core.util.StpClientUtil;
import com.ohos.connect.devicecloud.auth.core.util.StpLoginUserUtil;
import com.ohos.connect.devicecloud.auth.modular.login.enums.AuthExceptionEnum;
import com.ohos.connect.devicecloud.auth.modular.login.result.AuthPicValidCodeResult;
import com.ohos.connect.devicecloud.auth.modular.login.service.AuthService;
import com.ohos.connect.devicecloud.common.cache.CommonCacheOperator;
import com.ohos.connect.devicecloud.common.exception.CommonException;
import com.ohos.connect.devicecloud.common.util.CommonCryptogramUtil;
import com.ohos.connect.devicecloud.common.util.CommonEmailUtil;
import com.ohos.connect.devicecloud.dev.api.DevConfigApi;
import com.ohos.connect.devicecloud.dev.api.DevSmsApi;

import javax.annotation.Resource;
import java.util.Base64;

/**
 * 登录Service接口实现类
  * 
 */
@Service
public class AuthServiceImpl implements AuthService {

    private static final String OHOS_CONNECT_DEVICE_CLOUD_SYS_DEFAULT_CAPTCHA_OPEN_KEY = "OHOS_CONNECT_DEVICE_CLOUD_SYS_DEFAULT_CAPTCHA_OPEN";

    private static final String AUTH_VALID_CODE_CACHE_KEY = "auth-validCode:";

    private static final String LOGIN_ERROR_TIMES_KEY_PREFIX = "login-error-times:";

    @Resource(name = "appLoginUserApi")
    private SaBaseLoginUserApi appLoginUserApi;

    @Resource(name = "mqttApi")
    private MqttApi mqttApi;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private DevEmailApi devEmailApi;

    @Resource
    private DevSmsApi devSmsApi;

    @Resource
    private CommonCacheOperator commonCacheOperator;

    @Override
    public AuthPicValidCodeResult getPicCaptcha(String type) {
        // 生成验证码，随机4位字符
        CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(100, 38, 4, 10);
        // 定义返回结果
        AuthPicValidCodeResult authPicValidCodeResult = new AuthPicValidCodeResult();
        // 获取验证码的值
        String validCode = circleCaptcha.getCode();
        // 获取验证码的base64
        String validCodeBase64 = circleCaptcha.getImageBase64Data();
        // 生成请求号
        String validCodeReqNo = IdWorker.getIdStr();
        // 将base64返回前端
        authPicValidCodeResult.setValidCodeBase64(validCodeBase64);
        // 将请求号返回前端
        authPicValidCodeResult.setValidCodeReqNo(validCodeReqNo);
        // 将请求号作为key，验证码的值作为value放到redis，用于校验，5分钟有效
        commonCacheOperator.put(AUTH_VALID_CODE_CACHE_KEY + validCodeReqNo, validCode, 5 * 60);
        return authPicValidCodeResult;
    }

    @Override
    public String getPhoneValidCodeByPicCaptcha(AuthGetPhoneValidCodeParam authGetPhoneValidCodeParam, String type) {
        // 手机号
        String phone = authGetPhoneValidCodeParam.getPhone();
        // 验证码
        String validCode = authGetPhoneValidCodeParam.getValidCode();
        // 验证码请求号
        String validCodeReqNo = authGetPhoneValidCodeParam.getValidCodeReqNo();
        // 校验参数
        validPhoneValidCodeParam(null, validCode, validCodeReqNo, type);
        // 生成手机验证码的值，随机6为数字
        String phoneValidCode = RandomUtil.randomNumbers(6);
        // 生成手机验证码的请求号
        String phoneValidCodeReqNo = IdWorker.getIdStr();

        // TODO 使用阿里云执行发送验证码，将验证码作为短信内容的参数变量放入，
        // TODO 签名不传则使用系统默认配置的签名，支持传入多个参数，示例：{"name":"张三","number":"15038****76"}
        devSmsApi.sendSmsAliyun(phone, "互联互通", "SMS_474930993", JSONUtil.toJsonStr(JSONUtil.createObj().set("code", phoneValidCode)));

        // TODO 使用腾讯云执行发送验证码，将验证码作为短信内容的参数变量放入，
        // TODO sdkAppId和签名不传则使用系统默认配置的sdkAppId和签名，支持传入多个参数，逗号拼接，示例："张三,15038****76,进行中"
//        devSmsApi.sendSmsTencent("sdkAppId", phone, "签名", "模板编码", phoneValidCode);

        // 将请求号作为key，验证码的值作为value放到redis，用于校验，5分钟有效
        commonCacheOperator.put(AUTH_VALID_CODE_CACHE_KEY + phone + StrUtil.UNDERLINE + phoneValidCodeReqNo, phoneValidCode, 5 * 60);
        // 返回请求号
        return phoneValidCodeReqNo;
    }

    @Override
    public ValidCodeReqNoVO getPhoneValidCode(PhoneValidCodeQueryDTO phoneValidCodeQueryDTO, String type) {
        // 获取手机号
        String phone = phoneValidCodeQueryDTO.getPhone();
        // 校验手机号格式
        if(!PhoneUtil.isMobile(phone)) {
            throw new CommonException(AuthExceptionEnum.PHONE_FORMAT_ERROR.getValue());
        }
        // 生成手机验证码的值，随机6为数字
        String phoneValidCode = RandomUtil.randomNumbers(6);
        // 生成手机验证码的请求号
        String phoneValidCodeReqNo = IdWorker.getIdStr();
        // 将请求号作为key，验证码的值作为value放到redis，用于校验，5分钟有效
        commonCacheOperator.put(AUTH_VALID_CODE_CACHE_KEY + phone + StrUtil.UNDERLINE + phoneValidCodeReqNo, phoneValidCode, 5 * 60);

        String[] templateParams = {phoneValidCode};
        String signName = new String(Base64.getDecoder().decode("6bi/6JKZ55Sf5oCB5LqS6IGU5LqS6YCa"));
        devSmsApi.sendSmsHuaweiYun( phone, signName, "ac6bedc84839455baf9a3be0c4689fb2", templateParams);

        // 返回请求号
        ValidCodeReqNoVO validCodeReqNoVO = new ValidCodeReqNoVO();
        validCodeReqNoVO.setValidCodeReqNo(phoneValidCodeReqNo);
        return validCodeReqNoVO;
    }

    @Override
    public ValidCodeReqNoVO getEmailValidCode(String email, String type){
        if(ObjectUtil.isNull(email)){
            throw new CommonException("邮箱不能为空");
        }

        if(!CommonEmailUtil.isEmail(email)){
            throw new CommonException(AuthExceptionEnum.EMAIL_FORMAT_ERROR.getValue());
        }

        // 生成手机验证码的值，随机6位数字
        String validCode = RandomUtil.randomNumbers(6);
        // 生成手机验证码的请求号
        String validCodeReqNo = IdWorker.getIdStr();
        // 将请求号作为key，验证码的值作为value放到redis，用于校验，5分钟有效
        commonCacheOperator.put(AUTH_VALID_CODE_CACHE_KEY + email + StrUtil.UNDERLINE + validCodeReqNo, validCode, 5 * 60);

        // 向邮箱发送验证码
        DevEmailTemplateEnum emailValidateTemplate = DevEmailTemplateEnum.EMAIL_VALIDATE_CODE;
        devEmailApi.sendTextEmailLocal(email,emailValidateTemplate.getSubject(),String.format(emailValidateTemplate.getContent(),validCode), CollectionUtil.newArrayList());

        // 返回请求号
        ValidCodeReqNoVO validCodeReqNoVO = new ValidCodeReqNoVO();
        validCodeReqNoVO.setValidCodeReqNo(validCodeReqNo);
        return validCodeReqNoVO;
    }

    @Override
    public void validPhoneCode(ValidPhoneCodeDTO validPhoneCodeDTO) {
        validValidCode(validPhoneCodeDTO.getPhone(),validPhoneCodeDTO.getValidCode(),validPhoneCodeDTO.getValidCodeReqNo());
    }

    /**
     * 校验验证码方法
     *
     * 
     **/
    private void validValidCode(String phoneOrEmail, String validCode, String validCodeReqNo) {
        Object existValidCode;
        if(ObjectUtil.isEmpty(phoneOrEmail)) {
            throw new CommonException(AuthExceptionEnum.PHONE_NO_EMPTY.getValue());
        }

        existValidCode = commonCacheOperator.get(AUTH_VALID_CODE_CACHE_KEY + phoneOrEmail + StrUtil.UNDERLINE + validCodeReqNo);
        // 为空则直接验证码错误
        if(ObjectUtil.isEmpty(existValidCode)) {
            throw new CommonException(AuthExceptionEnum.VALID_CODE_ERROR.getValue());
        }
        // 不一致则直接验证码错误
        if (!validCode.equals(Convert.toStr(existValidCode).toLowerCase())) {
            throw new CommonException("验证码错误");
        }else{
            // 一致，则移除验证码
            commonCacheOperator.remove(AUTH_VALID_CODE_CACHE_KEY + phoneOrEmail + StrUtil.UNDERLINE + validCodeReqNo);
        }
    }

    /**
     * 校验手机号与验证码等参数
     *
     * 
     **/
    private void validPhoneValidCodeParam(String phoneOrEmail, String validCode, String validCodeReqNo, String type) {
        if(ObjectUtil.isEmpty(phoneOrEmail)) {
            throw new CommonException(AuthExceptionEnum.PHONE_NO_EMPTY.getValue());
        }

        // 校验手机号/验证码格式
        if(!PhoneUtil.isMobile(phoneOrEmail) && !CommonEmailUtil.isEmail(phoneOrEmail)) {
            throw new CommonException(AuthExceptionEnum.PHONE_FORMAT_ERROR.getValue());
        }
        // 校验验证码
        validValidCode(phoneOrEmail, validCode, validCodeReqNo);

        // 根据手机号获取用户信息，判断用户是否存在，根据APP判断
        if(ObjectUtil.isEmpty(appLoginUserApi.getClientUserByPhone(phoneOrEmail))) {
            throw new CommonException(AuthExceptionEnum.PHONE_ERROR.getValue());
        }
    }

    @Override
    public AuthAppLoginVO doLogin(AuthAppPwdDTO authAppPwdDTO, String type) {
        // 判断账号是否被封禁
        isDisableTime(authAppPwdDTO.getAccount());

        // 根据账号获取用户信息
        SaBaseClientLoginUser saBaseClientLoginUser = appLoginUserApi.getClientUserByAccount(authAppPwdDTO.getAccount());
        if(ObjectUtil.isEmpty(saBaseClientLoginUser)) {
            throw new CommonException(AuthExceptionEnum.ACCOUNT_ERROR.getValue());
        }

        // 校验密码哈希值
        String passwordHash = CommonCryptogramUtil.doHashValue(authAppPwdDTO.getPassword());
        if (!saBaseClientLoginUser.getPassword().equals(passwordHash)) {
            throw new CommonException(AuthExceptionEnum.PWD_ERROR.getValue());
        }

        // 执行APP登录
        String token = execLoginAPP(saBaseClientLoginUser,authAppPwdDTO.getDevice());

        // 为设备分配emqx帐号
        MqttAccountVO mqttAccountVO = mqttApi.getMqttAccountByDevice(authAppPwdDTO.getDevice());

        AuthAppLoginVO authAppLoginVO = new AuthAppLoginVO();
        authAppLoginVO.setToken(token);
        authAppLoginVO.setMqttAccountVO(mqttAccountVO);

        // 发送用户重复登录通知
        noticeUserLogin(authAppPwdDTO.getAccount(),mqttAccountVO.getClientId());

        return authAppLoginVO;
    }

    private void noticeUserLogin(String userAccount, String mqttClientId){
        String topic = "/user/"+ userAccount + "/signaltrans/v1/categories/data";

        // 构造header
        JSONObject header = new JSONObject();
        header.put("timestamp", Long.toString(System.currentTimeMillis()));
        header.put("category", "device");
        header.put("notifyType", "duplicateLogin");

        // 构造body
        JSONObject body = new JSONObject();
        body.put("mqttClientId", mqttClientId);

        JSONObject payload = new JSONObject();
        payload.put("header", header);
        payload.put("body", body);

        // 使用Gson将对象序列化为JSON字符串
        Gson gson = new Gson();
        String jsonPayload = gson.toJson(payload);

        // 创建MqttMessage并设置其内容
        MqttMessage message = new MqttMessage(jsonPayload.getBytes());

        try {
            mqttApi.sendMqttMessage(topic,message);
        }catch (MqttException e){
            e.printStackTrace();
        }

    }

    @Override
    public String doLoginByPhone(AuthPhoneValidCodeLoginDTO authPhoneValidCodeLoginDTO, String type) {
        // 手机号
        String phone = authPhoneValidCodeLoginDTO.getPhone();
        // 校验参数
        validPhoneValidCodeParam(phone, authPhoneValidCodeLoginDTO.getValidCode(), authPhoneValidCodeLoginDTO.getValidCodeReqNo(), type);
        // 设备
        String device = authPhoneValidCodeLoginDTO.getDevice();
        // 根据手机号获取用户信息
        SaBaseClientLoginUser saBaseClientLoginUser = appLoginUserApi.getClientUserByPhone(phone);
        if(ObjectUtil.isEmpty(saBaseClientLoginUser)) {
            throw new CommonException(AuthExceptionEnum.ACCOUNT_ERROR.getValue());
        }
        // 执行APP登录
        return execLoginAPP(saBaseClientLoginUser, device);
    }

    /**
     * 是否封禁状态
     * 如果被封禁了，执行以下逻辑，返回前端还需等待的时间
     */
    private void isDisableTime(String userAccount) {
        // disableTime = -2表示未被封禁
        long disableTime = StpUtil.getDisableTime(userAccount);
        if (disableTime > 0) {
            if (disableTime > 60) {
                throw new CommonException(userAccount + "账号已被封禁, 请再"+ disableTime/60+ "分钟后重新尝试登录!!");
            }
            throw new CommonException(userAccount + "账号已被封禁, 请再"+ disableTime+ "秒后重新尝试登录!!");
        }
    }

    // redis中保存登录错误次数
    private void saveLoginTimes(String userAccount){
        String loginErrorKey = LOGIN_ERROR_TIMES_KEY_PREFIX + userAccount;
        Integer number = (Integer) commonCacheOperator.get(loginErrorKey);
        if (number == null) {
            // 如果redis中没有保存，代表失败第一次
            number = 2;
            commonCacheOperator.put(loginErrorKey, number,5 * 60);
            return;
        }
        if (number < 5) {
            number++;
            commonCacheOperator.put(loginErrorKey, number,5 * 60);
            return;
        }
        // 第五次封禁账号,第六次进入isDisableTime方法，返回用户还需等待时间
        StpUtil.disable(userAccount, 5 * 60);
        // 删除redis 中的key
        clearLoginErrorTimes(userAccount);

    }

    /**
     * 登录成功、清空登录次数
     * @param userAccount 账号
     */
    private void clearLoginErrorTimes(String userAccount) {
        String loginErrorKey = LOGIN_ERROR_TIMES_KEY_PREFIX + userAccount;
        // 删除redis中的key
        commonCacheOperator.remove(loginErrorKey);
    }

    /**
     * 执行B端登录
     *
     *  
     * 
     **/
    private String execLoginB(SaBaseLoginUser saBaseLoginUser, String device) {
        // 校验状态
        if(!saBaseLoginUser.getEnabled()) {
            throw new CommonException(AuthExceptionEnum.ACCOUNT_DISABLED.getValue());
        }
        // 执行登录
        StpUtil.login(saBaseLoginUser.getId(), new SaLoginModel().setDevice(device).setExtra("name", saBaseLoginUser.getName()));
        // 缓存用户信息，此处使用TokenSession为了指定时间内无操作则自动下线
        StpUtil.getTokenSession().set("loginUser", saBaseLoginUser);
        // 返回token
        return StpUtil.getTokenInfo().tokenValue;
    }

    /**
     * 执行APP登录
     **/
    private String execLoginAPP(SaBaseClientLoginUser saBaseClientLoginUser, String device) {
        // 校验状态
        if(!saBaseClientLoginUser.getEnabled()) {
            throw new CommonException(AuthExceptionEnum.ACCOUNT_DISABLED.getValue());
        }
        // 执行登录
        StpClientUtil.login(saBaseClientLoginUser.getId(), new SaLoginModel().setDevice(device).setExtra("name", saBaseClientLoginUser.getName()));
        // 缓存用户信息，此处使用TokenSession为了指定时间内无操作则自动下线
        StpClientUtil.getTokenSession().set("loginUser", saBaseClientLoginUser);
        // 返回token
        return StpClientUtil.getTokenInfo().tokenValue;
    }

    /**
     * 获取B端登录用户信息
     *
     *  
     * 
     **/
    @Override
    public SaBaseLoginUser getLoginUser() {
        SaBaseLoginUser saBaseLoginUser = StpLoginUserUtil.getLoginUser();
        saBaseLoginUser.setPassword(null);
        saBaseLoginUser.setPermissionCodeList(null);
        saBaseLoginUser.setDataScopeList(null);
        return saBaseLoginUser;
    }

    /**
     * 获取APP登录用户信息
     *
     *  
     * 
     **/
    @Override
    public SaBaseClientLoginUser getClientLoginUser() {
        SaBaseClientLoginUser saBaseClientLoginUser = StpAppLoginUserUtil.getClientLoginUser();
        saBaseClientLoginUser.setPassword(null);
        saBaseClientLoginUser.setPermissionCodeList(null);
        saBaseClientLoginUser.setDataScopeList(null);
        return saBaseClientLoginUser;
    }

    @Override
    public String doLoginById(String userId, String device, String type) {
        SaBaseClientLoginUser saBaseClientLoginUser = appLoginUserApi.getClientUserById(userId);
        if (ObjectUtil.isEmpty(saBaseClientLoginUser)) {
            throw new CommonException(AuthExceptionEnum.ACCOUNT_ERROR.getValue());
        }
        // 执行APP登录
        return execLoginAPP(saBaseClientLoginUser, device);
    }

    @Override
    public void doLogout(String device) {
        if(Boolean.TRUE.equals(redisTemplate.hasKey("mqtt_user:" + device))){
            redisTemplate.delete("mqtt_user:"+device);
        }
        StpClientUtil.logout();
    }
}
